package utils

import (
	"fmt"
	"reflect"
)

// 获取类型的默认值
func GetDefaultValue(typ reflect.Type) any {
	return reflect.Zero(typ).Interface()
}

// 获取变量的 reflect.Value
func GetReflectValue(v interface{}) reflect.Value {
	if IsReflectValue(v) {
		if v.(reflect.Value).Kind() == reflect.Ptr {
			return v.(reflect.Value).Elem()
		}
		return v.(reflect.Value)
	}
	if IsPtr(v) {
		return reflect.ValueOf(v).Elem()
	}
	return reflect.ValueOf(v)
}

/**
 * 获取变量的 reflect.Type
 */
func GetReflectType(v interface{}) reflect.Type {
	if IsPtr(v) {
		return reflect.TypeOf(v).Elem()
	}
	return reflect.TypeOf(v)
}

// 可执行 ReflectValue 的调用
// 1. 方法中使用 reflect.Zero 的默认值， 采用默认值的参数必须使用指定的类型(即不能使用 any)
// 2. 使用默认值(不传递参数)时， 最后一个参数不建议使用切片（避免混淆， 默认值中最后一个切片会被认为是三点参数）
// 3. 使用默认值(不传递参数)时， 通过reflect反馈最后一个参数如果为 slice， 会被当成 三点参数(args ...) 来使用(不传递参数)
func CallRefValue(refV reflect.Value, args ...interface{}) (res []reflect.Value, err error) {
	if !refV.IsValid() {
		err = fmt.Errorf("%s is not valid function\n", refV.Type().Name())
		return
	}
	mArgs := make([]reflect.Value, len(args)) // 方法接收参数
	idx := 0
	for _, arg := range args {
		mArgs[idx] = reflect.ValueOf(arg)
		idx++
	}
	if numIn := refV.Type().NumIn(); numIn > idx {
		refVType := refV.Type()
		for ; idx < numIn; idx++ {
			if idx != numIn-1 || refVType.In(idx).Kind() != reflect.Slice {
				mArgs = append(mArgs, reflect.ValueOf(GetDefaultValue(refVType.In(idx))))
			}
		}
	}
	res = refV.Call(mArgs)
	return
}

// 函数的调用
// 参数的使用参考： CallRefValue
func CallFunc(obj any, args ...interface{}) (res []reflect.Value, err error) {
	refM := reflect.ValueOf(obj)
	if !refM.IsValid() {
		err = fmt.Errorf("%s is not valid function\n", reflect.TypeOf(obj).Name())
		return
	}
	return CallRefValue(refM, args...)
}

// 对象中方法的调用
// 参数的使用参考： CallRefValue
func CallMethod(obj any, method string, args ...interface{}) (res []reflect.Value, err error) {
	refV := reflect.ValueOf(obj)
	refM := refV.MethodByName(method)
	if !refM.IsValid() {
		err = fmt.Errorf("method %s is not found in %s\n", method, reflect.TypeOf(obj).Name())
		return
	}
	return CallRefValue(refM, args...)
}
